﻿namespace Home.Mvc.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Security.Claims;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Mvc;
    using Home.Data;
    using Home.Models;
    using Home.Mvc.Models;
    using Home.Repository;
    using Home.Security;
    using Home.Security.Authentication;
    using Microsoft.Owin.Security;

    [MvcHandleError]
    public class AccountController : Controller
    {
        private static readonly object CheckLoginStatusResultAuthorized = new { LoggedIn = true, Authorized = true };
        private static readonly object CheckLoginStatusResultNotAuthorized = new { LoggedIn = true, Authorized = false };
        private static readonly object CheckLoginStatusResultNotLoggedIn = new { LoggedIn = false, Authorized = false };

        [AllowAnonymous]
        public async Task<ActionResult> Login()
        {
            var model = new LoginViewModel()
            {
                LoginUris = new Dictionary<AuthenticationType, string>()
                {
                    { AuthenticationType.WeChat, await AuthenticationHelper.GenerateLoginUriAsync(AuthenticationType.WeChat) }
                }
            };

            if (this.Request.IsAjaxRequest())
            {
                return Json(model, JsonRequestBehavior.AllowGet);
            }

            return View("~/Mvc/Views/Account/Login.cshtml", model);
        }

        [AllowAnonymous]
        public async Task<ActionResult> LoginCallback()
        {
            var code = await AuthenticationHelper.GetAuthorizationCodeAsync(this.Request.GetOwinContext().Request.Uri.OriginalString);
            if (code == null || code.AuthenticationStatus != AuthenticationStatus.Passed)
            {
                return this.RedirectToRoute("LoginFailure");
            }

            var token = await AuthenticationHelper.GetAccessTokenAsync(code);
            if (token == null)
            {
                return this.RedirectToRoute("LoginFailure");
            }

            // add or update user in database
            var dbNow = DateTimeOffset.UtcNow;
            var dbUser = Thread.CurrentPrincipal.Identity.Name;
            var user = default(ApplicationUser);
            using (var database = new SecurityDbContext(true))
            {
                user = await database.Users.FindAsync(token.UserId);
                if (user == null)
                {
                    user = new ApplicationUser()
                    {
                        Id = token.UserId,
                        IdentityProvider = (byte)token.AuthenticationType,
                        Name = null,
                        Role = SecurityDbHelper.GetUserRole(token.UserId),
                    };
                    user.Create(database.Users, dbNow, dbUser, true);
                }
                else
                {
                    user.IdentityProvider = (byte)token.AuthenticationType;
                    user.Name = null;
                    user.Role = SecurityDbHelper.GetUserRole(token.UserId);
                    user.Update(database, database.Users, dbNow, dbUser);
                }

                await database.SaveChangesAsync();
            }

            // 
            var identityProviderName = SecurityDbHelper.GetIdentityProviderName(token.AuthenticationType);
            var identity = new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString("D", CultureInfo.InvariantCulture)),
                    new Claim(ClaimTypes.Name, user.Name),
                    new Claim(ClaimTypes.Role, user.Role.ToString()),
                    new Claim(SecurityHelper.IdentityProviderClaimType, identityProviderName),
                    new Claim(SecurityHelper.AuthenticationKeyClaimType, token.Id.ToString("D", CultureInfo.InvariantCulture)),
                },
                SecurityHelper.DefaultAuthenticationType);
            this.AuthenticationManager.SignIn(identity);

            return this.RedirectToRoute("LoginSuccess");
        }

        [AllowAnonymous]
        public async Task<ActionResult> CheckLoginStatus()
        {
            var identity = ((ClaimsIdentity)this.User.Identity);
            if (!this.User.Identity.IsAuthenticated)
            {
                return this.Json(CheckLoginStatusResultNotLoggedIn, JsonRequestBehavior.AllowGet);
            }

            var claim = identity.FindFirst(SecurityHelper.AuthenticationKeyClaimType);
            if (claim == null)
            {
                // the claim has problem, so need user to login again
                return this.Json(CheckLoginStatusResultNotLoggedIn, JsonRequestBehavior.AllowGet);
            }

            var id = default(Guid);
            if (!Guid.TryParse(claim.Value, out id))
            {
                // the claim has problem, so need user to login again
                return this.Json(CheckLoginStatusResultNotLoggedIn, JsonRequestBehavior.AllowGet);
            }

            var authenticated = await AuthenticationHelper.CheckAuthenticationStatus(id);
            if (!authenticated)
            {
                return this.Json(CheckLoginStatusResultNotAuthorized, JsonRequestBehavior.AllowGet);
            }

            return this.Json(CheckLoginStatusResultAuthorized, JsonRequestBehavior.AllowGet);
        }

        [Authorize]
        public async Task<ActionResult> RefreshLoginStatus()
        {
            var identity = ((ClaimsIdentity)this.User.Identity);
            var claim = identity.FindFirst(SecurityHelper.AuthenticationKeyClaimType);
            if (claim == null)
            {
                // the claim has problem, so need user to login again
                return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
            }

            var id = default(Guid);
            if (!Guid.TryParse(claim.Value, out id))
            {
                // the claim has problem, so need user to login again
                return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
            }

            var token = await AuthenticationHelper.GetRefreshTokenAsync(id);
            if (token == null)
            {
                // refresh token was failed, so user need to login again
                return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
            }

            return new HttpStatusCodeResult(HttpStatusCode.OK);
        }

        [AllowAnonymous]
        public ActionResult LoginFailure()
        {
            return View("~/Mvc/Views/Account/LoginFailure.cshtml");
        }

        [Authorize]
        public ActionResult LoginSuccess()
        {
            return View("~/Mvc/Views/Account/LoginSuccess.cshtml");
        }

        [Authorize]
        [HttpPost]
        [AjaxAwareValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            this.AuthenticationManager.SignOut(SecurityHelper.DefaultAuthenticationType);

            if (this.Request.IsAjaxRequest())
            {
                return new HttpStatusCodeResult(HttpStatusCode.OK);
            }

            return this.RedirectToLocal("/");
        }

        private IAuthenticationManager AuthenticationManager
        {
            get { return this.HttpContext.GetOwinContext().Authentication; }
        }

        private ActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }

            return Redirect("/");
        }
    }
}